package com.jorado.search.solr.client.inner;

import org.apache.commons.io.IOUtils;
import org.apache.http.*;
import org.apache.http.client.HttpClient;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.*;
import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.conn.HttpClientConnectionManager;
import org.apache.http.entity.BasicHttpEntity;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.InputStreamEntity;
import org.apache.http.entity.mime.FormBodyPart;
import org.apache.http.entity.mime.HttpMultipartMode;
import org.apache.http.entity.mime.MultipartEntity;
import org.apache.http.entity.mime.content.InputStreamBody;
import org.apache.http.entity.mime.content.StringBody;
import org.apache.http.message.BasicHeader;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;
import org.apache.solr.client.solrj.ResponseParser;
import org.apache.solr.client.solrj.SolrRequest;
import org.apache.solr.client.solrj.SolrServerException;
import org.apache.solr.client.solrj.V2RequestSupport;
import org.apache.solr.client.solrj.impl.*;
import org.apache.solr.client.solrj.request.RequestWriter;
import org.apache.solr.client.solrj.request.V2Request;
import org.apache.solr.common.SolrException;
import org.apache.solr.common.params.CommonParams;
import org.apache.solr.common.params.ModifiableSolrParams;
import org.apache.solr.common.params.SolrParams;
import org.apache.solr.common.util.Base64;
import org.apache.solr.common.util.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.lang.invoke.MethodHandles;
import java.net.ConnectException;
import java.net.SocketTimeoutException;
import java.nio.charset.StandardCharsets;
import java.security.Principal;
import java.util.*;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;

import static org.apache.solr.common.util.Utils.getObjectByPath;

/**
 * A SolrClient implementation that talks directly to a Solr server via HTTP
 */
public class HttpSolrClient extends BaseHttpSolrClient {

    private static final String UTF_8 = StandardCharsets.UTF_8.name();
    private static final String DEFAULT_PATH = "/select";
    private static final long serialVersionUID = -946812319974801896L;

    /**
     * User-Agent String.
     */
    public static final String AGENT = "Solr[" + HttpSolrClient.class.getName() + "] 1.0";

    private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());

    static final Class<HttpSolrClient> cacheKey = HttpSolrClient.class;

    /**
     * The URL of the Solr server.
     */
    protected volatile String baseUrl;

    /**
     * Default value: null / empty.
     * <p>
     * Parameters that are added to every request regardless. This may be a place
     * to add something like an authentication token.
     */
    protected ModifiableSolrParams invariantParams;

    /**
     * Default response parser is BinaryResponseParser
     * <p>
     * This parser represents the default Response Parser chosen to parse the
     * response if the parser were not specified as part of the request.
     *
     * @see org.apache.solr.client.solrj.impl.BinaryResponseParser
     */
    protected volatile ResponseParser parser;

    /**
     * The RequestWriter used to write all requests to Solr
     *
     * @see org.apache.solr.client.solrj.request.RequestWriter
     */
    protected volatile RequestWriter requestWriter = new BinaryRequestWriter();

    private final HttpClient httpClient;

    private volatile Boolean followRedirects = false;

    private volatile boolean useMultiPartPost;
    private final boolean internalClient;

    private volatile Set<String> queryParams = Collections.emptySet();
    private volatile Integer connectionTimeout;
    private volatile Integer soTimeout;

    /**
     * @deprecated use {@link HttpSolrClient#HttpSolrClient(Builder)} instead, as it is a more extension/subclassing-friendly alternative
     */
    @Deprecated
    protected HttpSolrClient(String baseURL, HttpClient client, ResponseParser parser, boolean allowCompression) {
        this(new Builder(baseURL)
                .withHttpClient(client)
                .withResponseParser(parser)
                .allowCompression(allowCompression));
    }

    /**
     * The constructor.
     *
     * @param baseURL          The base url to communicate with the Solr server
     * @param client           Http client instance to use for communication
     * @param parser           Response parser instance to use to decode response from Solr server
     * @param allowCompression Should compression be allowed ?
     * @param invariantParams  The parameters which should be included with every request.
     * @deprecated use {@link HttpSolrClient#HttpSolrClient(Builder)} instead, as it is a more extension/subclassing-friendly alternative
     */
    @Deprecated
    protected HttpSolrClient(String baseURL, HttpClient client, ResponseParser parser, boolean allowCompression,
                             ModifiableSolrParams invariantParams) {
        this(new Builder(baseURL)
                .withHttpClient(client)
                .withResponseParser(parser)
                .allowCompression(allowCompression)
                .withInvariantParams(invariantParams));
    }

    protected HttpSolrClient(Builder builder) {
        this.baseUrl = builder.baseSolrUrl;
        if (baseUrl.endsWith("/")) {
            baseUrl = baseUrl.substring(0, baseUrl.length() - 1);
        }
        if (baseUrl.indexOf('?') >= 0) {
            throw new RuntimeException(
                    "Invalid base url for solrj.  The base URL must not contain parameters: "
                            + baseUrl);
        }

        if (builder.httpClient != null) {
            this.httpClient = builder.httpClient;
            this.internalClient = false;
        } else {
            this.internalClient = true;
            ModifiableSolrParams params = new ModifiableSolrParams();
            params.set(HttpClientUtil.PROP_FOLLOW_REDIRECTS, followRedirects);
            params.set(HttpClientUtil.PROP_ALLOW_COMPRESSION, builder.compression);
            httpClient = HttpClientUtil.createClient(params);
        }

        this.parser = builder.responseParser;
        this.invariantParams = builder.invariantParams;
        this.connectionTimeout = builder.connectionTimeoutMillis;
        this.soTimeout = builder.socketTimeoutMillis;
    }

    public Set<String> getQueryParams() {
        return queryParams;
    }

    /**
     * Expert Method
     *
     * @param queryParams set of param keys to only send via the query string
     *                    Note that the param will be sent as a query string if the key is part
     *                    of this Set or the SolrRequest's query params.
     * @see org.apache.solr.client.solrj.SolrRequest#getQueryParams
     */
    public void setQueryParams(Set<String> queryParams) {
        this.queryParams = queryParams;
    }

    /**
     * Process the request. If
     * {@link org.apache.solr.client.solrj.SolrRequest#getResponseParser()} is
     * null, then use {@link #getParser()}
     *
     * @param request The {@link org.apache.solr.client.solrj.SolrRequest} to process
     * @return The {@link org.apache.solr.common.util.NamedList} result
     * @throws IOException If there is a low-level I/O error.
     * @see #request(org.apache.solr.client.solrj.SolrRequest,
     * org.apache.solr.client.solrj.ResponseParser)
     */
    @Override
    public NamedList<Object> request(final SolrRequest request, String collection)
            throws SolrServerException, IOException {
        ResponseParser responseParser = request.getResponseParser();
        if (responseParser == null) {
            responseParser = parser;
        }
        return request(request, responseParser, collection);
    }

    public NamedList<Object> request(final SolrRequest request, final ResponseParser processor) throws SolrServerException, IOException {
        return request(request, processor, null);
    }

    public NamedList<Object> request(final SolrRequest request, final ResponseParser processor, String collection)
            throws SolrServerException, IOException {
        HttpRequestBase method = createMethod(request, collection);
        setBasicAuthHeader(request, method);
        return executeMethod(method, request.getUserPrincipal(), processor, isV2ApiRequest(request));
    }

    private boolean isV2ApiRequest(final SolrRequest request) {
        return request instanceof V2Request || request.getPath().contains("/____v2");
    }

    private void setBasicAuthHeader(SolrRequest request, HttpRequestBase method) throws UnsupportedEncodingException {
        if (request.getBasicAuthUser() != null && request.getBasicAuthPassword() != null) {
            String userPass = request.getBasicAuthUser() + ":" + request.getBasicAuthPassword();
            String encoded = Base64.byteArrayToBase64(userPass.getBytes(UTF_8));
            method.setHeader(new BasicHeader("Authorization", "Basic " + encoded));
        }
    }

    /**
     * @lucene.experimental
     */
    public static class HttpUriRequestResponse {
        public HttpUriRequest httpUriRequest;
        public Future<NamedList<Object>> future;
    }

    /**
     * @lucene.experimental
     */
    public HttpUriRequestResponse httpUriRequest(final SolrRequest request)
            throws SolrServerException, IOException {
        ResponseParser responseParser = request.getResponseParser();
        if (responseParser == null) {
            responseParser = parser;
        }
        return httpUriRequest(request, responseParser);
    }

    /**
     * @lucene.experimental
     */
    public HttpUriRequestResponse httpUriRequest(final SolrRequest request, final ResponseParser processor) throws SolrServerException, IOException {
        HttpUriRequestResponse mrr = new HttpUriRequestResponse();
        final HttpRequestBase method = createMethod(request, null);
        ExecutorService pool = ExecutorUtil.newMDCAwareFixedThreadPool(1, new SolrjNamedThreadFactory("httpUriRequest"));
        try {
            MDC.put("HttpSolrClient.url", baseUrl);
            mrr.future = pool.submit(() -> executeMethod(method, request.getUserPrincipal(), processor, isV2ApiRequest(request)));

        } finally {
            pool.shutdown();
            MDC.remove("HttpSolrClient.url");
        }
        assert method != null;
        mrr.httpUriRequest = method;
        return mrr;
    }

    protected ModifiableSolrParams calculateQueryParams(Set<String> queryParamNames,
                                                        ModifiableSolrParams wparams) {
        ModifiableSolrParams queryModParams = new ModifiableSolrParams();
        if (queryParamNames != null) {
            for (String param : queryParamNames) {
                String[] value = wparams.getParams(param);
                if (value != null) {
                    for (String v : value) {
                        queryModParams.add(param, v);
                    }
                    wparams.remove(param);
                }
            }
        }
        return queryModParams;
    }

    protected HttpRequestBase createMethod(SolrRequest request, String collection) throws IOException, SolrServerException {
        if (request instanceof V2RequestSupport) {
            request = ((V2RequestSupport) request).getV2Request();
        }
        SolrParams params = request.getParams();
        RequestWriter.ContentWriter contentWriter = requestWriter.getContentWriter(request);
        Collection<ContentStream> streams = contentWriter == null ? requestWriter.getContentStreams(request) : null;
        String path = requestWriter.getPath(request);
        if (path == null || !path.startsWith("/")) {
            path = DEFAULT_PATH;
        }

        ResponseParser parser = request.getResponseParser();
        if (parser == null) {
            parser = this.parser;
        }

        // The parser 'wt=' and 'version=' params are used instead of the original
        // params
        ModifiableSolrParams wparams = new ModifiableSolrParams(params);
        if (parser != null) {
            wparams.set(CommonParams.WT, parser.getWriterType());
            wparams.set(CommonParams.VERSION, parser.getVersion());
        }
        if (invariantParams != null) {
            wparams.add(invariantParams);
        }

        String basePath = baseUrl;
        if (collection != null)
            basePath += "/" + collection;

        if (request instanceof V2Request) {
            if (System.getProperty("solr.v2RealPath") == null) {
                basePath = baseUrl.replace("/solr", "/api");
            } else {
                basePath = baseUrl + "/____v2";
            }
        }

        if (SolrRequest.METHOD.GET == request.getMethod()) {
            if (streams != null || contentWriter != null) {
                throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "GET can't send streams!");
            }

            return new HttpGet(basePath + path + wparams.toQueryString());
        }

        if (SolrRequest.METHOD.DELETE == request.getMethod()) {
            return new HttpDelete(basePath + path + wparams.toQueryString());
        }

        if (SolrRequest.METHOD.POST == request.getMethod() || SolrRequest.METHOD.PUT == request.getMethod()) {

            String url = basePath + path;
            boolean hasNullStreamName = false;
            if (streams != null) {
                for (ContentStream cs : streams) {
                    if (cs.getName() == null) {
                        hasNullStreamName = true;
                        break;
                    }
                }
            }
            boolean isMultipart = ((this.useMultiPartPost && SolrRequest.METHOD.POST == request.getMethod())
                    || (streams != null && streams.size() > 1)) && !hasNullStreamName;

            LinkedList<NameValuePair> postOrPutParams = new LinkedList<>();

            if (contentWriter != null) {
                String fullQueryUrl = url + wparams.toQueryString();
                HttpEntityEnclosingRequestBase postOrPut = SolrRequest.METHOD.POST == request.getMethod() ?
                        new HttpPost(fullQueryUrl) : new HttpPut(fullQueryUrl);
                postOrPut.addHeader("Content-Type",
                        contentWriter.getContentType());
                postOrPut.setEntity(new BasicHttpEntity() {
                    @Override
                    public boolean isStreaming() {
                        return true;
                    }

                    @Override
                    public void writeTo(OutputStream outstream) throws IOException {
                        contentWriter.write(outstream);
                    }
                });
                return postOrPut;

            } else if (streams == null || isMultipart) {
                // send server list and request list as query string params
                ModifiableSolrParams queryParams = calculateQueryParams(this.queryParams, wparams);
                queryParams.add(calculateQueryParams(request.getQueryParams(), wparams));
                String fullQueryUrl = url + queryParams.toQueryString();
                HttpEntityEnclosingRequestBase postOrPut = fillContentStream(request, streams, wparams, isMultipart, postOrPutParams, fullQueryUrl);
                return postOrPut;
            }
            // It is has one stream, it is the post body, put the params in the URL
            else {
                String fullQueryUrl = url + wparams.toQueryString();
                HttpEntityEnclosingRequestBase postOrPut = SolrRequest.METHOD.POST == request.getMethod() ?
                        new HttpPost(fullQueryUrl) : new HttpPut(fullQueryUrl);
                fillSingleContentStream(streams, postOrPut);

                return postOrPut;
            }
        }

        throw new SolrServerException("Unsupported method: " + request.getMethod());

    }

    private void fillSingleContentStream(Collection<ContentStream> streams, HttpEntityEnclosingRequestBase postOrPut) throws IOException {
        // Single stream as body
        // Using a loop just to get the first one
        final ContentStream[] contentStream = new ContentStream[1];
        for (ContentStream content : streams) {
            contentStream[0] = content;
            break;
        }
        Long size = contentStream[0].getSize();
        postOrPut.setEntity(new InputStreamEntity(contentStream[0].getStream(), size == null ? -1 : size) {
            @Override
            public Header getContentType() {
                return new BasicHeader("Content-Type", contentStream[0].getContentType());
            }

            @Override
            public boolean isRepeatable() {
                return false;
            }
        });

    }

    private HttpEntityEnclosingRequestBase fillContentStream(SolrRequest request, Collection<ContentStream> streams, ModifiableSolrParams wparams, boolean isMultipart, LinkedList<NameValuePair> postOrPutParams, String fullQueryUrl) throws IOException {
        HttpEntityEnclosingRequestBase postOrPut = SolrRequest.METHOD.POST == request.getMethod() ?
                new HttpPost(fullQueryUrl) : new HttpPut(fullQueryUrl);

        if (!isMultipart) {
            postOrPut.addHeader("Content-Type",
                    "application/x-www-form-urlencoded; charset=UTF-8");
        }

        List<FormBodyPart> parts = new LinkedList<>();
        Iterator<String> iter = wparams.getParameterNamesIterator();
        while (iter.hasNext()) {
            String p = iter.next();
            String[] vals = wparams.getParams(p);
            if (vals != null) {
                for (String v : vals) {
                    if (isMultipart) {
                        parts.add(new FormBodyPart(p, new StringBody(v, StandardCharsets.UTF_8)));
                    } else {
                        postOrPutParams.add(new BasicNameValuePair(p, v));
                    }
                }
            }
        }

        // TODO: remove deprecated - first simple attempt failed, see {@link MultipartEntityBuilder}
        if (isMultipart && streams != null) {
            for (ContentStream content : streams) {
                String contentType = content.getContentType();
                if (contentType == null) {
                    contentType = BinaryResponseParser.BINARY_CONTENT_TYPE; // default
                }
                String name = content.getName();
                if (name == null) {
                    name = "";
                }
                parts.add(new FormBodyPart(name,
                        new InputStreamBody(
                                content.getStream(),
                                ContentType.parse(contentType),
                                content.getName())));
            }
        }

        if (parts.size() > 0) {
            MultipartEntity entity = new MultipartEntity(HttpMultipartMode.STRICT);
            for (FormBodyPart p : parts) {
                entity.addPart(p);
            }
            postOrPut.setEntity(entity);
        } else {
            //not using multipart
            postOrPut.setEntity(new UrlEncodedFormEntity(postOrPutParams, StandardCharsets.UTF_8));
        }
        return postOrPut;
    }

    private static final List<String> errPath = Arrays.asList("metadata", "error-class");//Utils.getObjectByPath(err, false,"metadata/error-class")

    protected NamedList<Object> executeMethod(HttpRequestBase method, Principal userPrincipal, final ResponseParser processor, final boolean isV2Api) throws SolrServerException {
        method.addHeader("User-Agent", AGENT);

        org.apache.http.client.config.RequestConfig.Builder requestConfigBuilder = HttpClientUtil.createDefaultRequestConfigBuilder();
        if (soTimeout != null) {
            requestConfigBuilder.setSocketTimeout(soTimeout);
        }
        if (connectionTimeout != null) {
            requestConfigBuilder.setConnectTimeout(connectionTimeout);
        }
        if (followRedirects != null) {
            requestConfigBuilder.setRedirectsEnabled(followRedirects);
        }

        method.setConfig(requestConfigBuilder.build());

        HttpEntity entity = null;
        InputStream respBody = null;
        boolean shouldClose = true;
        try {
            // Execute the method.
            HttpClientContext httpClientRequestContext = HttpClientUtil.createNewHttpClientRequestContext();
            if (userPrincipal != null) {
                // Normally the context contains a static userToken to enable reuse resources.
                // However, if a personal Principal object exists, we use that instead, also as a means
                // to transfer authentication information to Auth plugins that wish to intercept the request later
                httpClientRequestContext.setUserToken(userPrincipal);
            }
            final HttpResponse response = httpClient.execute(method, httpClientRequestContext);

            int httpStatus = response.getStatusLine().getStatusCode();

            // Read the contents
            entity = response.getEntity();
            respBody = entity.getContent();
            Header ctHeader = response.getLastHeader("content-type");
            String contentType;
            if (ctHeader != null) {
                contentType = ctHeader.getValue();
            } else {
                contentType = "";
            }

            // handle some http level checks before trying to parse the response
            switch (httpStatus) {
                case HttpStatus.SC_OK:
                case HttpStatus.SC_BAD_REQUEST:
                case HttpStatus.SC_CONFLICT:  // 409
                    break;
                case HttpStatus.SC_MOVED_PERMANENTLY:
                case HttpStatus.SC_MOVED_TEMPORARILY:
                    if (!followRedirects) {
                        throw new SolrServerException("Server at " + getBaseURL()
                                + " sent back a redirect (" + httpStatus + ").");
                    }
                    break;
                default:
                    if (processor == null || "".equals(contentType)) {
                        throw new RemoteSolrException(baseUrl, httpStatus, "non ok status: " + httpStatus
                                + ", message:" + response.getStatusLine().getReasonPhrase(),
                                null);
                    }
            }
            if (processor == null || processor instanceof InputStreamResponseParser) {

                // no processor specified, return raw stream
                NamedList<Object> rsp = new NamedList<>();
                rsp.add("stream", respBody);
                rsp.add("closeableResponse", response);
                // Only case where stream should not be closed
                shouldClose = false;
                return rsp;
            }

            String procCt = processor.getContentType();
            if (procCt != null) {
                String procMimeType = ContentType.parse(procCt).getMimeType().trim().toLowerCase(Locale.ROOT);
                String mimeType = ContentType.parse(contentType).getMimeType().trim().toLowerCase(Locale.ROOT);
                if (!procMimeType.equals(mimeType)) {
                    // unexpected mime type
                    String msg = "Expected mime type " + procMimeType + " but got " + mimeType + ".";
                    Header encodingHeader = response.getEntity().getContentEncoding();
                    String encoding;
                    if (encodingHeader != null) {
                        encoding = encodingHeader.getValue();
                    } else {
                        encoding = "UTF-8"; // try UTF-8
                    }
                    try {
                        msg = msg + " " + IOUtils.toString(respBody, encoding);
                    } catch (IOException e) {
                        throw new RemoteSolrException(baseUrl, httpStatus, "Could not parse response with encoding " + encoding, e);
                    }
                    throw new RemoteSolrException(baseUrl, httpStatus, msg, null);
                }
            }

            NamedList<Object> rsp = null;
            String charset = EntityUtils.getContentCharSet(response.getEntity());
            try {
                rsp = processor.processResponse(respBody, charset);
            } catch (Exception e) {
                throw new RemoteSolrException(baseUrl, httpStatus, e.getMessage(), e);
            }
            Object error = rsp == null ? null : rsp.get("error");
            if (error != null && (isV2Api || String.valueOf(getObjectByPath(error, true, errPath)).endsWith("ExceptionWithErrObject"))) {
                throw RemoteExecutionException.create(baseUrl, rsp);
            }
            if (httpStatus != HttpStatus.SC_OK && !isV2Api) {
                NamedList<String> metadata = null;
                String reason = null;
                try {
                    NamedList err = (NamedList) rsp.get("error");
                    if (err != null) {
                        reason = (String) err.get("msg");
                        if (reason == null) {
                            reason = (String) err.get("trace");
                        }
                        metadata = (NamedList<String>) err.get("metadata");
                    }
                } catch (Exception ex) {
                }
                if (reason == null) {
                    StringBuilder msg = new StringBuilder();
                    msg.append(response.getStatusLine().getReasonPhrase())
                            .append("\n\n")
                            .append("request: ")
                            .append(method.getURI());
                    reason = java.net.URLDecoder.decode(msg.toString(), UTF_8);
                }
                RemoteSolrException rss = new RemoteSolrException(baseUrl, httpStatus, reason, null);
                if (metadata != null) rss.setMetadata(metadata);
                throw rss;
            }
            return rsp;
        } catch (ConnectException e) {
            throw new SolrServerException("Server refused connection at: "
                    + getBaseURL(), e);
        } catch (SocketTimeoutException e) {
            throw new SolrServerException(
                    "Timeout occurred while waiting response from server at: "
                            + getBaseURL(), e);
        } catch (IOException e) {
            throw new SolrServerException(
                    "IOException occurred when talking to server at: " + getBaseURL(), e);
        } finally {
            if (shouldClose) {
                Utils.consumeFully(entity);
            }
        }
    }

    // -------------------------------------------------------------------
    // -------------------------------------------------------------------

    /**
     * Retrieve the default list of parameters are added to every request
     * regardless.
     *
     * @see #invariantParams
     */
    public ModifiableSolrParams getInvariantParams() {
        return invariantParams;
    }

    public String getBaseURL() {
        return baseUrl;
    }

    /**
     * Change the base-url used when sending requests to Solr.
     * <p>
     * Two different paths can be specified as a part of this URL:
     * <p>
     * 1) A path pointing directly at a particular core
     * <pre>
     *   httpSolrClient.setBaseURL("http://my-solr-server:8983/solr/core1");
     *   QueryResponse resp = httpSolrClient.query(new SolrQuery("*:*"));
     * </pre>
     * Note that when a core is provided in the base URL, queries and other requests can be made without mentioning the
     * core explicitly.  However, the client can only send requests to that core.
     * <p>
     * 2) The path of the root Solr path ("/solr")
     * <pre>
     *   httpSolrClient.setBaseURL("http://my-solr-server:8983/solr");
     *   QueryResponse resp = httpSolrClient.query("core1", new SolrQuery("*:*"));
     * </pre>
     * In this case the client is more flexible and can be used to send requests to any cores.  The cost of this is that
     * the core must be specified on each request.
     */
    public void setBaseURL(String baseURL) {
        this.baseUrl = baseURL;
    }

    public ResponseParser getParser() {
        return parser;
    }

    /**
     * Note: This setter method is <b>not thread-safe</b>.
     *
     * @param processor Default Response Parser chosen to parse the response if the parser
     *                  were not specified as part of the request.
     * @see org.apache.solr.client.solrj.SolrRequest#getResponseParser()
     */
    public void setParser(ResponseParser processor) {
        parser = processor;
    }

    /**
     * Return the HttpClient this instance uses.
     */
    public HttpClient getHttpClient() {
        return httpClient;
    }

    /**
     * HttpConnectionParams.setConnectionTimeout
     *
     * @param timeout Timeout in milliseconds
     * @deprecated since 7.0  Use {@link Builder} methods instead.
     */
    @Deprecated
    public void setConnectionTimeout(int timeout) {
        this.connectionTimeout = timeout;
    }

    /**
     * Set SoTimeout (read timeout). This is desirable
     * for queries, but probably not for indexing.
     *
     * @param timeout Timeout in milliseconds
     *                <p>
     *                s   * @deprecated since 7.0  Use {@link Builder} methods instead.
     */
    @Deprecated
    public void setSoTimeout(int timeout) {
        this.soTimeout = timeout;
    }

    /**
     * Configure whether the client should follow redirects or not.
     * <p>
     * This defaults to false under the assumption that if you are following a
     * redirect to get to a Solr installation, something is misconfigured
     * somewhere.
     * </p>
     */
    public void setFollowRedirects(boolean followRedirects) {
        this.followRedirects = followRedirects;
    }

    public void setRequestWriter(RequestWriter requestWriter) {
        this.requestWriter = requestWriter;
    }

    /**
     * Close the {@link HttpClientConnectionManager} from the internal client.
     */
    @Override
    public void close() throws IOException {
        if (httpClient != null && internalClient) {
            HttpClientUtil.close(httpClient);
        }
    }

    public boolean isUseMultiPartPost() {
        return useMultiPartPost;
    }

    /**
     * Set the multipart connection properties
     */
    public void setUseMultiPartPost(boolean useMultiPartPost) {
        this.useMultiPartPost = useMultiPartPost;
    }


    /**
     * @deprecated since 8.0, catch {@link BaseHttpSolrClient.RemoteSolrException} instead
     */
    @Deprecated
    public static class RemoteSolrException extends BaseHttpSolrClient.RemoteSolrException {

        public RemoteSolrException(String remoteHost, int code, String msg, Throwable th) {
            super(remoteHost, code, msg, th);
        }
    }

    /**
     * @deprecated since 8.0, catch {@link BaseHttpSolrClient.RemoteExecutionException} instead
     */
    @Deprecated
    public static class RemoteExecutionException extends BaseHttpSolrClient.RemoteExecutionException {

        public RemoteExecutionException(String remoteHost, int code, String msg, NamedList meta) {
            super(remoteHost, code, msg, meta);
        }
    }

    /**
     * Constructs {@link HttpSolrClient} instances from provided configuration.
     */
    public static class Builder extends SolrClientBuilder<Builder> {
        protected String baseSolrUrl;
        protected boolean compression;
        protected ModifiableSolrParams invariantParams = new ModifiableSolrParams();

        public Builder() {
            this.responseParser = new BinaryResponseParser();
        }

        /**
         * Specify the base-url for the created client to use when sending requests to Solr.
         * <p>
         * Two different paths can be specified as a part of this URL:
         * <p>
         * 1) A path pointing directly at a particular core
         * <pre>
         *   SolrClient client = builder.withBaseSolrUrl("http://my-solr-server:8983/solr/core1").build();
         *   QueryResponse resp = client.query(new SolrQuery("*:*"));
         * </pre>
         * Note that when a core is provided in the base URL, queries and other requests can be made without mentioning the
         * core explicitly.  However, the client can only send requests to that core.
         * <p>
         * 2) The path of the root Solr path ("/solr")
         * <pre>
         *   SolrClient client = builder.withBaseSolrUrl("http://my-solr-server:8983/solr").build();
         *   QueryResponse resp = client.query("core1", new SolrQuery("*:*"));
         * </pre>
         * In this case the client is more flexible and can be used to send requests to any cores.  This flexibility though
         * requires that the core is specified on all requests.
         */
        public Builder withBaseSolrUrl(String baseSolrUrl) {
            this.baseSolrUrl = baseSolrUrl;
            return this;
        }

        /**
         * Create a Builder object, based on the provided Solr URL.
         * <p>
         * Two different paths can be specified as a part of this URL:
         * <p>
         * 1) A path pointing directly at a particular core
         * <pre>
         *   SolrClient client = new HttpSolrClient.Builder("http://my-solr-server:8983/solr/core1").build();
         *   QueryResponse resp = client.query(new SolrQuery("*:*"));
         * </pre>
         * Note that when a core is provided in the base URL, queries and other requests can be made without mentioning the
         * core explicitly.  However, the client can only send requests to that core.
         * <p>
         * 2) The path of the root Solr path ("/solr")
         * <pre>
         *   SolrClient client = new HttpSolrClient.Builder("http://my-solr-server:8983/solr").build();
         *   QueryResponse resp = client.query("core1", new SolrQuery("*:*"));
         * </pre>
         * In this case the client is more flexible and can be used to send requests to any cores.  This flexibility though
         * requires that the core be specified on all requests.
         * <p>
         * By default, compression is not enabled on created HttpSolrClient objects.
         */
        public Builder(String baseSolrUrl) {
            this.baseSolrUrl = baseSolrUrl;
            this.responseParser = new BinaryResponseParser();
        }

        /**
         * Chooses whether created {@link HttpSolrClient}s use compression by default.
         */
        public Builder allowCompression(boolean compression) {
            this.compression = compression;
            return this;
        }

        /**
         * Use a delegation token for authenticating via the KerberosPlugin
         */
        public Builder withKerberosDelegationToken(String delegationToken) {
            if (this.invariantParams.get(DelegationTokenHttpSolrClient.DELEGATION_TOKEN_PARAM) != null) {
                throw new IllegalStateException(DelegationTokenHttpSolrClient.DELEGATION_TOKEN_PARAM + " is already defined!");
            }
            this.invariantParams.add(DelegationTokenHttpSolrClient.DELEGATION_TOKEN_PARAM, delegationToken);
            return this;
        }

        /**
         * Adds to the set of params that the created {@link HttpSolrClient} will add on all requests
         *
         * @param params a set of parameters to add to the invariant-params list.  These params must be unique and may not
         *               duplicate a param already in the invariant list.
         */
        public Builder withInvariantParams(ModifiableSolrParams params) {
            Objects.requireNonNull(params, "params must be non null!");

            for (String name : params.getParameterNames()) {
                if (this.invariantParams.get(name) != null) {
                    throw new IllegalStateException("parameter " + name + " is redefined.");
                }
            }

            this.invariantParams.add(params);
            return this;
        }

        /**
         * Create a {@link HttpSolrClient} based on provided configuration.
         */
        public HttpSolrClient build() {
            if (baseSolrUrl == null) {
                throw new IllegalArgumentException("Cannot create HttpSolrClient without a valid baseSolrUrl!");
            }

            if (this.invariantParams.get(DelegationTokenHttpSolrClient.DELEGATION_TOKEN_PARAM) == null) {
                return new HttpSolrClient(this);
            } else {
                return new DelegationTokenHttpSolrClient(this);
            }
        }

        @Override
        public Builder getThis() {
            return this;
        }
    }
}
